/**
 * 
 */
package spatial.geometry;

import java.awt.geom.Point2D;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * This is a set of map of <line, intersections> where "intersections" is a
 * sorted set of the line's intersections with other lines.
 * 
 * The purpose of this data structure is to speedup on the computing, updating
 * the intersections' levels information.
 * 
 * This can be seen as an index of intersections based on the lines they are on.
 * 
 * @author wu wei
 * 
 */
public class IntersectionsIndexByLine {

	Map<LineFunction, TreeSet<LeveledIntersection>> line2Intsecs;

	public IntersectionsIndexByLine() {
		this.line2Intsecs = new HashMap<LineFunction, TreeSet<LeveledIntersection>>();
	}

	/**
	 * Add the given wip to its two lines' intersection sets.
	 * 
	 * @param wip
	 */
	public void add(LeveledIntersection wip) {
		/**
		 * <pre>
		 * add wip to its line1's intersection set 
		 * add wip to its line2's intersection set
		 * </pre>
		 */
		this.addToLine(wip, wip.getLine1());
		this.addToLine(wip, wip.getLine2());
	}

	private void addToLine(LeveledIntersection wip, LineFunction line) {
		/**
		 * <pre>
		 * check whether map of line exist 
		 * if not: construct 
		 * add to line's intersection set
		 * </pre>
		 */
		if (!this.line2Intsecs.containsKey(line)) {
			this.line2Intsecs.put(line, new TreeSet<LeveledIntersection>());
		}
		this.line2Intsecs.get(line).add(wip);
	}

	/**
	 * remove the given wip from its two lines' intersection sets.
	 * 
	 * @param wip
	 */
	public void remove(LeveledIntersection wip) {
		/**
		 * <pre>
		 * remove wip from its line1's intersection set 
		 * remove wip from its line2's intersection set
		 * </pre>
		 */
		this.removeFromLine(wip, wip.getLine1());
		this.removeFromLine(wip, wip.getLine2());
	}

	/**
	 * Remove the given line from this line-intersections map. note that both
	 * the intersections on it and the line itself will be removed.
	 * 
	 * @param line
	 */
	public void removeLine(LineFunction line) {
		Set<LeveledIntersection> intersections = this.getIntersections(line);
		for (LeveledIntersection wip : intersections) {
			/**
			 * <pre>
			 * remove from the other line 
			 * </pre>
			 */
			if (line.equals(wip.getLine1())) {
				this.removeFromLine(wip, wip.getLine2());
			} else if (line.equals(wip.getLine2())) {
				this.removeFromLine(wip, wip.getLine1());
			} else {
				throw new IllegalArgumentException(
						"The implementation of this class is not right: the contract (that an intersection is indexed on its two lines) does not hold");
			}
		}
		this.line2Intsecs.remove(line);
	}

	@Override
	public String toString() {
		String s = "begin line2Intsecs\n";
		String sep = "\n";
		for (LineFunction key : this.line2Intsecs.keySet()) {
			s += "\t";
			s += key.toString();
			s += ":";
			s += this.line2Intsecs.get(key);
			s += sep;
		}
		s += "end line2Intsecs";
		return s;
	}

	private void removeFromLine(LeveledIntersection wip, LineFunction line) {
		/**
		 * <pre>
		 * get line's set 
		 * if not available: complain 
		 * remove from line's set 
		 * if not in the set: complain
		 * 
		 * <pre>
		 */
		Set<LeveledIntersection> intSecs = this.line2Intsecs.get(line);
		if (intSecs == null) {
			throw new IllegalArgumentException(
					"the wip you try to move is not in the data structure");
		}
		if (!intSecs.remove(wip)) {
			throw new IllegalArgumentException(
					"the wip you try to move is not in the data structure");
		}
	}

	/**
	 * get the given line's intersection set (a tree-set instance).
	 * 
	 * @param line
	 * @return
	 */
	public TreeSet<LeveledIntersection> getIntersections(LineFunction line) {
		/**
		 * <pre>
		 * get line's intersections 
		 * if not available: complain 
		 * return the intersections
		 * 
		 * <pre>
		 */
		TreeSet<LeveledIntersection> intSecs = this.line2Intsecs.get(line);
		if (intSecs == null) {
			throw new IllegalArgumentException(
					"the given line is not in the data structure");
		}
		return intSecs;
	}

	public Set<LeveledIntersection> getAllIntersections() {
		Set<LeveledIntersection> itSet = new HashSet<LeveledIntersection>();
		for (TreeSet<LeveledIntersection> its : this.line2Intsecs.values()) {
			itSet.addAll(its);
		}
		return itSet;
	}

	public Set<LineFunction> getLines() {
		return this.line2Intsecs.keySet();
	}

	/**
	 * Get the intersection points that are of interest to the given level's
	 * convex hull.
	 * 
	 * Pre-condition: each line's intersections are sorted with their
	 * coordinates.
	 * 
	 * Side-effect: none.
	 * 
	 * @param level
	 * @return
	 */
	public Set<Point2D.Double> getConvexHullPoints(int level) {
		/**
		 * <pre>
		 * for each of the line->intersections
		 * 	get the intersections on the line that are of interest to the given level convex hull
		 * 		start from left to right (intersections sorted w.r.t x values)
		 * 		left-interesting point is the first one with level no greater than the given level
		 * 		right-interesting point is the last one with level no greater than the given level
		 *  add them into return set if such interesting points are found
		 * end for
		 * </pre>
		 */
		int highest = 0;
		HashSet<Point2D.Double> ps = new HashSet<Point2D.Double>();
		for (TreeSet<LeveledIntersection> lineWips : this.line2Intsecs.values()) {
			assert (lineWips.size() >= 2) : "A line surely will have at least two intersections.";
			LeveledIntersection left = null, right = null;
			for (LeveledIntersection wip : lineWips) {
				if (wip.getLevel() > highest)
					highest = wip.getLevel();
				if (left == null)
					if (wip.getLevel() <= level)
						left = wip;
				if (wip.getLevel() <= level)
					right = wip;
			}
			if (left != null) {
				ps.add(left);
			}
			if (right != null) {
				ps.add(right);
			}
		}
		if (ps.size() < 3 && level < highest) {
			System.err.println("Something bad happened!");
			return this.getConvexHullPoints(level + 1);
		}
		return ps;
	}

}
